<!DOCTYPE html>
<title>Test preservesPitch.</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resources/testdriver.js"></script>
<script src="/resources/testdriver-vendor.js"></script>
<script src="pitch-detector.js"></script>
<script>

// Remove when media-elements/historical.html's preservePitch prefix tests are are passing.
function getPreservesPitch(audio) {
    if ("preservesPitch" in HTMLAudioElement.prototype) {
        return audio.preservesPitch;
    }
    if ("webkitPreservesPitch" in HTMLAudioElement.prototype) {
        return audio.webkitPreservesPitch;
    }
    return undefined;
}

// Remove when media-elements/historical.html's preservePitch prefix tests are are passing.
function setPreservesPitch(audio, value) {
    if ("preservesPitch" in HTMLAudioElement.prototype) {
        audio.preservesPitch = value;
    } else if ("webkitPreservesPitch" in HTMLAudioElement.prototype) {
        audio.webkitPreservesPitch = value;
    }
}

test(function(t) {
    assert_true("preservesPitch" in HTMLAudioElement.prototype);
}, "Test that preservesPitch is present and unprefixed.");

test(function(t) {
    let defaultAudio = document.createElement('audio');
    assert_true(getPreservesPitch(defaultAudio));

    setPreservesPitch(defaultAudio, false);
    assert_false(getPreservesPitch(defaultAudio));
}, "Test that preservesPitch is on by default");


var audio;

function addTestCleanups(t, detector) {
    t.add_cleanup(() => {
        audio.pause();
        audio.currentTime = 0;
    });
    t.add_cleanup(() => detector.cleanup());
}

function testPreservesPitch(preservesPitch, playbackRate, expectedPitch, description) {
    promise_test(async t => {
        let detector = getPitchDetector(audio);
        addTestCleanups(t, detector);

        audio.playbackRate = playbackRate;
        setPreservesPitch(audio, preservesPitch);

        function waitUntil(time) {
            return new Promise((resolve) => {
                audio.ontimeupdate = () => {
                    if (audio.currentTime >= time) {
                        resolve();
                    }
                };
            });
        }

        // Wait until we have played some audio. Otherwise, the detector
        // might return a pitch of 0Hz.
        audio.play();
        await waitUntil(0.5);

        var pitch = detector.detect();

        // 25Hz is larger than the margin we get from 48kHz and 44.1kHz
        // audio being analyzed by a FFT of size 2048. If we get something
        // different, there is an error within the test's calculations (or
        // we might be dealing a larger sample rate).
        assert_less_than(pitch.margin, 25,
            "Test error: the margin should be reasonably small.")

        // Allow for a 15% margin of error in the pitch detector, to reduce test
        // flakiness. Since our tests speed up and slow down by a factor of 2,
        // this should be plenty of leeway, without causing false negatives.
        assert_approx_equals(pitch.value, expectedPitch, expectedPitch*0.15,
            "The actual pitch should be close to the expected pitch.");

    }, description);
}

var REFERENCE_PITCH = 440;

promise_test(async t => {
    // Create the audio element only once, in order to lower the chances of
    // tests timing out.
    audio = document.createElement('audio');

    // This file contains 5 seconds of a 440hz sine wave.
    audio.src = "/media/sine440.mp3";

    let detector = getPitchDetector(audio);
    addTestCleanups(t, detector);

    // The first time we run the test, we need to interact with the
    // AudioContext and Audio element via user gestures.
    await test_driver.bless("Play audio element", () => {
        return Promise.all([audio.play(), detector.ensureStart()]);
    });
}, "Setup Audio element and AudioContext")

testPreservesPitch(true, 1.0, REFERENCE_PITCH,
    "The default playbackRate should not affect pitch")

testPreservesPitch(false, 1.0, REFERENCE_PITCH,
    "The default playbackRate should not affect pitch, even with preservesPitch=false")

testPreservesPitch(true, 2.0, REFERENCE_PITCH,
    "Speed-ups should not change the pitch when preservesPitch=true")

testPreservesPitch(true, 0.5, REFERENCE_PITCH,
    "Slow-downs should not change the pitch when preservesPitch=true")

testPreservesPitch(false, 2.0, REFERENCE_PITCH*2.0,
    "Speed-ups should change the pitch when preservesPitch=false")

testPreservesPitch(false, 0.5, REFERENCE_PITCH*0.5,
    "Slow-downs should change the pitch when preservesPitch=false")

</script>
